package main

import (
	"global/structs"
	"sync"
	"time"
	"github.com/kataras/iris"
	"global/function"
	"github.com/garyburd/redigo/redis"
	"encoding/json"
	"fmt"
	"github.com/shopspring/decimal"
	"sort"
	"github.com/ngaut/log"
)

var timeTypeMaps = map[string]int{
	"1m":  1,
	"3m":  2,
	"5m":  5,
	"15m": 15,
	"30m": 30,
	"1h":  1,
	"2h":  2,
	"4h":  4,
	"6h":  6,
	"12h": 12,
	"1d":  1,
	"3d":  3,
	"1w":  1,
}

const TIME_TICKER = time.Second * 3

type klineWorker struct {
	k        *Kline
	name     string
	coinPair structs.CoinPair
	wg       sync.WaitGroup
}

func NewKlineWorker(k *Kline, coinPair structs.CoinPair) *klineWorker {
	return &klineWorker{
		k:        k,
		name:     coinPair.Name,
		coinPair: coinPair,
	}
}

func (w *klineWorker) start() {
	log.Infof("coin pair [%s] start worker", w.name)
	ticker := time.NewTicker(TIME_TICKER)

	go func() {
		for {
			select {
			case <-ticker.C:
				w.run()
			}
		}
	}()

	iris.RegisterOnInterrupt(func() {
		log.Infof("ticker stoped")
		ticker.Stop()
	})
}

type klineItem struct {
	Timestamp 		int64				`json:"timestamp"`
	Open 			decimal.Decimal		`json:"open"`
	High 			decimal.Decimal		`json:"high"`
	Low 			decimal.Decimal		`json:"low"`
	Close 			decimal.Decimal		`json:"close"`
	Number 			decimal.Decimal		`json:"number"`
}

func (w *klineWorker) run()  {

	cache := w.k.GetRedisPool("ote").Get()
	if cache == nil {
		log.Error("get redis pool error")
		return
	}

	orders := getOteOrders(cache, w.name)
	defer cache.Close()

	if len(orders) == 0 {
		return
	}

	w.wg.Add(len(timeTypeMaps))
	for t := range timeTypeMaps {
		go func(timeType string) {
			conn := w.k.GetRedisPool("ote").Get()
			defer func() {
				w.wg.Done()
				conn.Close()
			}()

			kLineItems := make(map[int64]klineItem)
			for _, order := range orders {
				if order.Timestamp == 0 {
					continue
				}

				timestamp := formatStringTimeToUnix(order.Timestamp, timeType)
				if timestamp == 0 {
					log.Errorf("timestamp not right and continued, timeType: %s, nowTime: %d, timeStamp: %d", timeType, time.Now().Unix(), timestamp)
					continue
				}

				price, _ := decimal.NewFromString(order.Price)
				number, _ := decimal.NewFromString(order.Number)

				if v, ok := kLineItems[timestamp]; !ok {
					kLineItems[timestamp] = klineItem{
						Timestamp: 		timestamp,
						Open: 			price,
						High: 			price,
						Low: 			price,
						Close: 			price,
						Number: 		number,
					}
				} else {

					high, low := v.High, v.Low
					if price.GreaterThan(v.High) {
						high = price
					}
					if price.LessThan(v.Low) {
						low = price
					}
					kLineItems[timestamp] = klineItem{
						Timestamp: 		timestamp,
						Open: 			v.Open,
						High: 			high,
						Low: 			low,
						Close: 			price,
						Number: 		number.Add(v.Number),
					}
				}
			}

			save(conn, w.name, timeType, kLineItems)
		}(t)
	}
	w.wg.Wait()
}

type ResultData [6]float64

func save(conn redis.Conn, coinPair, timeType string, items map[int64]klineItem) {

	klineMapKey := function.GetKlineCacheMapKey(coinPair)
	oldKlineMapDatas, err := redis.String(conn.Do("HGET", klineMapKey, timeType))
	if err != nil {
		log.Errorf("get old kline map data error, msg: %s", err.Error())
	}

	data := make(map[int64]klineItem)
	if oldKlineMapDatas != "" {
		err := json.Unmarshal([]byte(oldKlineMapDatas), &data)
		if err != nil {
			log.Errorf("json unmarshal old kline map data error, msg: %s", err.Error())
		}
	}

	// 追加到原来数据，如果有重复则比较

	for k, v := range items {
		if val, ok := data[k]; ok {
			newExistsData := val
			if val.High.LessThan(v.High) {
				newExistsData.High = v.High
			}
			if val.Low.GreaterThan(v.Low) {
				newExistsData.Low = v.Low
			}
			newExistsData.Close = v.Close
			newExistsData.Number = val.Number.Add(v.Number)
			data[k] = newExistsData
		} else {
			data[k] = v
		}
	}

	// 先将 order 倒序
	timestamps := make([]int64, 0)
	for _, value := range data {
		timestamps = append(timestamps, value.Timestamp)
	}

	// 排序 逆序取最新数据
	sort.Slice(timestamps, func(i, j int) bool {
		return timestamps[i] > timestamps[j]
	})

	// 保留最新150条
	if len(timestamps) >= 150 {
		timestamps = timestamps[:150]
	}

	var newMapData = make(map[int64]klineItem)
	var newData = make([]ResultData, 0)

	// 客户端要求顺序排列
	sort.Slice(timestamps, func(i, j int) bool {
		return timestamps[i] < timestamps[j]
	})

	for _, timestamp := range timestamps {
		if timestamp == 0 { continue }
		newMapData[timestamp] = data[timestamp]
		value := data[timestamp]
		open, _ := value.Open.RoundBank(12).Float64()
		high, _ := value.High.RoundBank(12).Float64()
		low, _ := value.Low.RoundBank(12).Float64()
		c, _ := value.Close.RoundBank(12).Float64()
		number, _ := value.Number.RoundBank(4).Float64()

		newData = append(newData, ResultData{float64(value.Timestamp) * 1000, open, high, low, c, number})
	}

	// map
	if mapStr, err := json.Marshal(newMapData); err == nil {
		_, err := conn.Do("HSET", klineMapKey, timeType, string(mapStr))
		if err != nil {
			log.Errorf("save kline map data error, msg: %s", err.Error())
			return
		}
	} else {
		log.Errorf("json marshal kline map data error, msg: %s", err.Error())
		return
	}

	// data
	if resultStr, err := json.Marshal(newData); err == nil {
		_, err = conn.Do("HSET", function.GetKlineCacheDataKey(coinPair), timeType, string(resultStr))
		if err != nil {
			log.Errorf("save kline all item error, msg: %s", err.Error())
			return
		}
	} else {
		log.Errorf("json marshal kline data error, msg: %s", err.Error())
		return
	}


	// last
	if len(newData) > 0 {
		last, err := json.Marshal(newData[len(newData) - 1])
		if err != nil {
			log.Errorf("json marshal last kline item error, msg: %s", err.Error())
			return
		}

		_, err = conn.Do("HSET", function.GetKlineCacheLastKey(coinPair), timeType, string(last))
		if err != nil {
			log.Errorf("save kline last item error, msg: %s", err.Error())
		}
	}

}

func getOteOrders(conn redis.Conn, name string) []structs.Order {

	oteOrderListKey := function.GetOteOrderList(name)

	kLineDatas := make([]structs.Order, 0)

	// 1 取数据
	orders, err := redis.Strings(conn.Do("LRANGE", oteOrderListKey, 0, -1))
	if err != nil {
		log.Errorf("get ote order list error, msg: %s", err.Error())
		return kLineDatas
	}

	length := len(orders)
	if length == 0 {
		return kLineDatas
	}

	_, err = conn.Do("LTRIM", oteOrderListKey, length, -1)
	if err != nil {
		log.Errorf("ltrim ote order list error, msg: %s", err.Error())
		return kLineDatas
	}

	for _, v := range orders {
		var data structs.Order
		err := json.Unmarshal([]byte(v), &data)
		if err != nil {
			log.Errorf("json unmarshal kline data error, msg: %s", err.Error())
			continue
		}
		kLineDatas = append(kLineDatas, data)
	}

	return kLineDatas
}



func formatStringTimeToUnix(timestamp int64, ts string) int64 {

	in, ok := timeTypeMaps[ts]

	if !ok {
		return 0
	}

	var ftime string
	stamp := time.Unix(timestamp, 0)

	switch ts {
	case "1m":
		ftime = stamp.Format("2006-01-02 15:04:00")
	case "30m", "15m", "5m", "3m":
		i := stamp.Minute()
		c := i - i%in
		if c == 0 {
			ftime = stamp.Format("2006-01-02 15:00:00")
		} else {
			suffix := fmt.Sprintf("%d", c)
			if c < 10 {
				suffix = fmt.Sprintf("0%d", c)
			}
			ftime = stamp.Format("2006-01-02 15") + ":" + suffix + ":00"
		}
	case "12h", "6h", "4h", "2h", "1h":
		h := stamp.Hour()
		c := h - h%in
		if c == 0 {
			ftime = stamp.Format("2006-01-02 00:00:00")
		} else {
			suffix := fmt.Sprintf("%d", c)
			if c < 10 {
				suffix = fmt.Sprintf("0%d", c)
			}
			ftime = stamp.Format("2006-01-02 ") + suffix + ":00:00"
		}
	case "1d":
		ftime = stamp.Format("2006-01-02 00:00:00")
	case "3d":
		yearDay := stamp.YearDay()
		c := yearDay % 3
		ftime = stamp.Add(-1 * time.Duration(c) * time.Hour).Format("2006-01-02 00:00:00")
	case "1w":
		w := time.Unix(timestamp, 0).Weekday()
		ftime = stamp.Add(-1 * time.Duration(w) * time.Hour).Format("2006-01-02 00:00:00")
	}

	p, err := time.ParseInLocation("2006-01-02 15:04:05", ftime, time.Local)
	if err != nil {
		log.Errorf("parse time error: %s", err.Error())
		return 0
	}

	return p.Unix()
}














